﻿#region << 版 本 注 释 >>
/*----------------------------------------------------------------
 * 版权所有 (c) 2022 北京超维景生物科技有限公司 保留所有权利。
 * CLR版本：4.0.30319.42000
 * 文件名：Kernel
 * 
 * 创建者：huangyang
 * 电子邮箱：huangyang@tvscope.cn
 * 创建时间：2023/2/8 11:19:58
 * 版本：V1.0.0
 * 描述：
 *
 * ----------------------------------------------------------------
 * 修改人：
 * 时间：
 * 修改说明：
 *
 * 版本：V1.0.1
 *----------------------------------------------------------------*/
#endregion << 版 本 注 释 >>

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using ImageK.Exceptions;
using ImageK.Gui;
using ImageK.IO;
using ImageK.Java;
using ImageK.Macros;
using ImageK.Plugin;
using ImageK.Plugin.Filter;
using ImageK.Plugin.Frame;
using ImageK.Process;
using ImageK.Text;
using ImageK.Util;

namespace ImageK
{
    public class IJ
    {
        /** SansSerif, plain, 10-point font */
        public static Font font10 = new Font("SansSerif",  10, FontStyle.Regular);
        /** SansSerif, plain, 12-point font */
        public static Font font12 = ImageK.SansSerif12;
        /** SansSerif, plain, 14-point font */
        // public static Font font14 = ImageK.SansSerif14;

        /** Image display modes */
        public const int COMPOSITE = 1, COLOR = 2, GRAYSCALE = 3;

        public const string URL = "http://imagej.nih.gov/ij";
        public const Keys ALL_KEYS = Keys.None;

        /** Use setDebugMode(boolean) to enable/disable debug mode. */
        public static bool debugMode;

        public static bool hideProcessStackDialog;

        public const char micronSymbol = '\u00B5';
        public const char angstromSymbol = '\u00C5';
        public const char degreeSymbol = '\u00B0';

        private static ImageK _ij;

        // private static java.applet.Applet applet;
        private static Gui.ProgressBar progressBar;
        private static TextPanel _textPanel = null;
        private static string osname, osarch;

        private static bool _isMac = false;
        private static bool _isWin = true;
        private static bool _isLinux = false;

        private static int javaVersion;

        private static bool controlDown, altDown, spaceDown, shiftDown;

        private static bool _macroRunning;

        private static Thread previousThread;
        private static TextPanel logPanel;
        // private static ClassLoader classLoader;
        private static bool memMessageDisplayed;
        private static long _maxMemory;
        private static bool _escapePressed;
        private static bool _redirectErrorMessages;
        private static bool suppressPluginNotFoundError;
        private static Dictionary<string,string> commandTable;
        private static List<object> eventListeners = new List<object>();
        private static string lastErrorMessage;


        private static string smoothMacro;
        private static Interpreter macroInterpreter;
        private static bool _protectStatusBar;
        private static Thread statusBarThread;

        public static void Init(ImageK imagej)
        {
            _ij = imagej;
            Menus m = new Menus(_ij);
            Prefs.load(m);
            m.addMenuBar();

            progressBar = _ij.getProgressBar();
        }

        public static void Cleanup()
        {
            _ij = null;
        }

        /**Returns a reference to the "ImageJ" frame.*/
        public static ImageK GetInstance()
        {
            return _ij;
        }

        /** Runs the specified plugin using the specified image. */
        public static Object runPlugIn(ImagePlus imp, String className, String arg)
        {
            if (imp!=null)
            {
                ImagePlus temp = WindowManager.getTempCurrentImage();
                WindowManager.setTempCurrentImage(imp);
                Object o = runPlugIn("", className, arg);
                WindowManager.setTempCurrentImage(temp);
                return o;
            }
            else
                return runPlugIn(className, arg);
        }
        /** Runs the specified plugin and returns a reference to it. */
        public static Object runPlugIn(String className, String arg)
        {
            return runPlugIn("", className, arg);
        }

        /** Runs the specified plugin and returns a reference to it. */
        public static object runPlugIn(string commandName, string className, string arg)
        {
            if (arg == null) arg = "";
            if (IJ.debugMode)
                IJ.log("runPlugIn: " + className + argument(arg));
            // Load using custom classloader if this is a user 
            // plugin and we are not running as an applet
            if (!className.StartsWith("ImageK."))
            {
                //执行用户自定义Plugin
                return runUserPlugIn(commandName, className, arg, false);
            }
            dynamic thePlugIn = null;
            try
            {
                thePlugIn = ClassHelper.CreateInstance<PlugIn>(className, "");
                if (thePlugIn is PlugIn)
                {
                    if (thePlugIn is PlugInDialog dig)
                    {
                        ((PlugInDialog)thePlugIn).run(arg);
                    }
                    else
                    {
                        ((PlugIn)thePlugIn).run(arg);
                    }
                    
                }
                else
                    new PlugInFilterRunner(thePlugIn, commandName, arg);
            }
            catch (Exception e)
            {
                //todo:
                // if (!(className != null && className.StartsWith("ij.plugin.MacAdapter")))
                // {
                //     log("Plugin or class not found: \"" + className + "\"\n(" + e + ")");
                //     String path = Prefs.getCustomPropsPath();
                //     if (path != null) ;
                //     log("Error may be due to custom properties at " + path);
                // }
            }
            // catch (InstantiationException e) { log("Unable to load plugin (ins)"); }
            // catch (IllegalAccessException e) { log("Unable to load plugin, possibly \nbecause it is not public."); }
            _redirectErrorMessages = false;
            return thePlugIn;
        }

        public static object runUserPlugIn(string commandName, string className, string arg, bool createNewLoader)
        {
            if (IJ.debugMode)
                IJ.log("runUserPlugIn: " + className + ", arg=" + argument(arg));
            // if (applet != null) return null;
            // if (createNewLoader)
            //     classLoader = null;
            // ClassLoader loader = getClassLoader();
            dynamic thePlugIn = null;
            try
            {
                thePlugIn = ClassHelper.CreateInstance<PlugIn>(className, "");
                if (thePlugIn is PlugIn)
                {
                    ((PlugIn)thePlugIn).run(arg);
                }
                else if (thePlugIn is PlugInFilter)
                {
                    new PlugInFilterRunner(thePlugIn, commandName, arg);
                }
                
            }
            catch (Exception e)
            {
                //todo:
                // if (className.startsWith("macro:"))
                //     runMacro(className.substring(6));
                // else if (className.contains("_") && !suppressPluginNotFoundError)
                //     error("Plugin or class not found: \"" + className + "\"\n(" + e + ")");
            }
            // catch (NoClassDefFoundError e)
            // {
            //     int dotIndex = className.indexOf('.');
            //     if (dotIndex >= 0 && className.contains("_"))
            //     {
            //         // rerun plugin after removing folder name
            //         if (debugMode) IJ.log("runUserPlugIn: rerunning " + className);
            //         return runUserPlugIn(commandName, className.substring(dotIndex + 1), arg, createNewLoader);
            //     }
            //     if (className.contains("_") && !suppressPluginNotFoundError)
            //         error("Run User Plugin", "Class not found while attempting to run \"" + className + "\"\n \n   " + e);
            // }
            // catch (InstantiationException e) { error("Unable to load plugin (ins)"); }
            // catch (IllegalAccessException e) { error("Unable to load plugin, possibly \nbecause it is not public."); }
            if (thePlugIn != null && !"HandleExtraFileTypes".Equals(className))
                _redirectErrorMessages = false;
            suppressPluginNotFoundError = false;
            return thePlugIn;
        }

        private static string argument(string arg)
        {
            return arg != null && !arg.Equals("") && !arg.Contains("\n") ? "(\"" + arg + "\")" : "";
        }


        /** Runs a menu command on a separete thread and returns immediately. */
        public static void doCommand(string command)
        {
            new Executer(command, null);
        }

        /** Runs an ImageJ command using the specified image and options.
            To generate run() calls, start the recorder (Plugins/Macro/Record)
            and run commands from the ImageJ menu bar.*/
        public static void run(ImagePlus imp, String command, String options)
        {
            //todo:

            // if (ij == null && Menus.getCommands() == null)
            //     init();
            // if (imp != null)
            // {
            //     ImagePlus temp = WindowManager.getTempCurrentImage();
            //     WindowManager.setTempCurrentImage(imp);
            //     run(command, options);
            //     WindowManager.setTempCurrentImage(temp);
            // }
            // else
            //     run(command, options);
        }

        static void init()
        {
            Menus m = new Menus(null);
            Prefs.load(m);
            m.addMenuBar();
        }

        private static void testAbort()
        {
            if (Macro._abort)
                abort();
        }

        /** Returns true if the run(), open() or newImage() method is executing. */
        public static bool MacroRunning()
        {
            return _macroRunning;
        }

        /** Returns true if a macro is running, or if the run(), open() 
        or newImage() method is executing. */
        public static bool isMacro()
        {
            return _macroRunning || Interpreter.getInstance() != null;
        }

        /**Returns the Applet that created this ImageJ or null if running as an application.*/
        // public static java.applet.Applet getApplet()
        // {
        //     return applet;
        // }
        public static object getApplet()
        {
            return null;
        }

        /// <summary>
        /// Displays a message in the ImageJ status bar. If 's' starts 
        /// with '!', subsequent showStatus() calls in the current
        /// thread(without "!" in the message) are suppressed.
        /// </summary>
        /// <param name="s"></param>
        public static void ShowStatus(string s)
        {
            if ((Interpreter.getInstance() == null && statusBarThread == null)
                || (statusBarThread != null && Thread.CurrentThread != statusBarThread))
                protectStatusBar(false);
            bool doProtect = s.StartsWith("!"); // suppress subsequent showStatus() calls
            if (doProtect)
            {
                protectStatusBar(true);
                statusBarThread = Thread.CurrentThread;
                s = s.Substring(1);
            }
            if (doProtect || !_protectStatusBar)
            {
                if (_ij != null)
                    _ij.showStatus(s);
                ImagePlus imp = WindowManager.getCurrentImage();
                //todo:
                // ImageCanvas ic = imp != null ? imp.getCanvas() : null;
                // if (ic != null)
                //     ic.setShowCursorStatus(s.Length == 0 ? true : false);
            }
        }

        /**	Updates the progress bar, where 0<=progress<=1.0. The progress bar is 
        not shown in BatchMode and erased if progress>=1.0. The progress bar is
        updated only if more than 90 ms have passes since the last call. Does nothing 
        if the ImageJ window is not present. */
        public static void showProgress(double progress)
        {
            if (progressBar != null) progressBar.show(progress, false);
        }

        /**	Updates the progress bar, where the length of the bar is set to
         * (<code>currentValue+1)/finalValue</code> of the maximum bar length.
         * The bar is erased if <code>currentValue&gt;=finalValue</code>. 
         * The bar is updated only if more than 90 ms have passed since the
         * last call. Displays subordinate progress bars as dots if
         * 'currentIndex' is negative (example: Plugins/Utilities/Benchmark).
        */
        public static void showProgress(int currentIndex, int finalIndex)
        {
            //todo:
            // if (progressBar != null)
            // {
            //     progressBar.show(currentIndex, finalIndex);
            //     if (currentIndex == finalIndex)
            //         progressBar.setBatchMode(false);
            // }
        }

        public static void ShowTime(ImagePlus imp, long start, string str, int nslices)
        {
            //todo:
        }

        public static void setInputEvent()
        {
            //todo:
            // altDown = e.isAltDown();
            // shiftDown = e.isShiftDown();
        }

        /** Returns true if this machine is a Macintosh. */
        public static bool isMacintosh()
        {
            return _isMac;
        }

        /** Returns true if this machine is a Macintosh running OS X. */
        public static bool isMacOSX()
        {
            return isMacintosh();
        }

        /** Returns true if this machine is running Windows. */
        public static bool IsWindows()
        {
            return _isWin;
        }

        /** Returns true if ImageJ is running on a Java 1.8 or greater JVM. */
        public static bool isJava18()
        {
            return javaVersion >= 8;
        }

        /** Returns true if ImageJ is running on Linux. */
        public static bool isLinux()
        {
            return _isLinux;
        }

        /** Returns true if ImageJ is running a 64-bit version of Java. */
        public static bool is64Bit()
        {
            // if (osarch==null)
            //     osarch = System.getProperty("os.arch");
            // return osarch!=null && osarch.indexOf("64")!=-1;
            return System.Environment.Is64BitOperatingSystem;
        }

        /**Delays 'msecs' milliseconds.*/
        public static void wait(int msecs)
        {
            try
            {
                Thread.Sleep(msecs);
            }
            catch (Exception e) { }
        }

        /** Emits an audio beep. */
        public static void beep()
        {
            // java.awt.Toolkit.getDefaultToolkit().beep();
        }

        public static string freeMemory()
        {
            var currentProcess = System.Diagnostics.Process.GetCurrentProcess();
            long inUse = currentProcess.WorkingSet64;
            string inUseStr = inUse < 10000 * 1024 ? inUse / 1024L + "K" : inUse / 1048576L + "MB";
            string maxStr = "";
            long max = currentProcess.PeakWorkingSet64;
            if (max > 0L)
            {
                double percent = (double)inUse * 100 / max;
                maxStr = " of " + max / 1048576L + "MB (" + (percent < 1.0 ? "<1" : percent.ToString("F0")) + "%)";
            }
            return inUseStr + maxStr;
        }

        /** Returns the amount of memory currently being used by ImageJ. */
        public static long currentMemory()
        {
            // long freeMem = Runtime.getRuntime().freeMemory();
            // long totMem = Runtime.getRuntime().totalMemory();
            // return totMem-freeMem;

            var currentProcess = System.Diagnostics.Process.GetCurrentProcess();
            long usedMemory = currentProcess.WorkingSet64;
            return usedMemory;
        }

        /** Returns the maximum amount of memory available to ImageJ or
            zero if ImageJ is unable to determine this limit. */
        public static long maxMemory()
        {
            // if (maxMemory==0L)
            // {
            //     Memory mem = new Memory();
            //     maxMemory = mem.getMemorySetting();
            //     if (maxMemory==0L) maxMemory = mem.maxMemory();
            // }
            // return maxMemory;

            var currentProcess = System.Diagnostics.Process.GetCurrentProcess();
            _maxMemory = currentProcess.PeakWorkingSet64;
            return _maxMemory;
        }

        public static void log(string s)
        {
            if (string.IsNullOrEmpty(s)) return;
            if (logPanel==null && _ij!=null) {
            	TextWindow logWindow = new TextWindow("Log", "", 400, 250);
            	logPanel = logWindow.getTextPanel();
            	logPanel.Font = new Font("SansSerif", 16, FontStyle.Regular);
            }
            if (logPanel!=null) {
                if (s.StartsWith("\\"))
                {
                    //todo:
                    // handleLogCommand(s);
                }
                else {
            		if (s.EndsWith("\n")) {
            			if (s.Equals("\n\n"))
            				s= "\n \n ";
            			else if (s.EndsWith("\n\n"))
            				s = s.Substring(0, s.Length-2)+"\n \n ";
            			else
            				s = s+" ";
            		}
            		logPanel.append(s);
            	}
            } else {
            	// LogStream.redirectSystem(false);
                Console.WriteLine(s);
            }
        }

        /** Displays a message in a dialog box titled "ImageJ". If a 
        macro or JavaScript is running, it is aborted. Writes to the
        Java console if the ImageJ window is not present.*/
        public static void error(String msg)
        {
            error(null, msg);
            //todo:
            // if (Thread.currentThread().getName().endsWith("JavaScript"))
            //     throw new RuntimeException(Macro.MACRO_CANCELED);
            // else
            //     Macro.abort();
        }

        /** Displays a message in a dialog box with the specified title. If a 
        macro or JavaScript is running, it is aborted. Writes to the
        Java console if the ImageJ window is not present. */
        public static void error(string title, string msg)
        {

        }

        /** Displays a message in a dialog box with the specified title.
        Returns false if the user pressed "Cancel". */
        public static bool showMessageWithCancel(string title, string msg)
        {
            var result = MessageBox.Show(msg, title, MessageBoxButtons.OKCancel);
            return result != DialogResult.Cancel;
        }

        /** Displays a stack trace. Use the setExceptionHandler 
        method() to override with a custom exception handler. */
        public static void handleException(Exception e)
        {
            //todo:
        }

        /**Displays a "no images are open" dialog box.*/
        public static void noImage()
        {
            string msg = "There are no images open";
            //todo:
            // if (macroInterpreter != null)
            // {
            //     macroInterpreter.abort(msg);
            //     macroInterpreter = null;
            // }
            // else
                error("No Image", msg);
        }

        /** Displays an "out of memory" message to the "Log" window. */
        public static void outOfMemory(string name)
        {
            //todo:
            // Undo.reset();
            // System.gc();
            // lastErrorMessage = "out of memory";
            // String tot = Runtime.getRuntime().maxMemory() / 1048576L + "MB";
            // if (!memMessageDisplayed)
            //     log(">>>>>>>>>>>>>>>>>>>>>>>>>>>");
            // log("<Out of memory>");
            // if (!memMessageDisplayed)
            // {
            //     log("<All available memory (" + tot + ") has been>");
            //     log("<used. To make more available, use the>");
            //     log("<Edit>Options>Memory & Threads command.>");
            //     log(">>>>>>>>>>>>>>>>>>>>>>>>>>>");
            //     memMessageDisplayed = true;
            // }
            // Macro.abort();
        }

        /** Returns true if the space bar is down. */
        public static bool spaceBarDown()
        {
            return spaceDown;
        }

        /** Returns true if the control key is down. */
        public static bool controlKeyDown()
        {
            return controlDown;
        }

        /** Returns true if the alt key is down. */
        public static bool altKeyDown()
        {
            return altDown;
        }

        /** Returns true if the shift key is down. */
        public static bool shiftKeyDown()
        {
            return shiftDown;
        }

        public static void setKeyDown(Keys key)
        {
            switch (key)
            {
                case Keys.Control:
                    controlDown = true;
                    break;
                // case KeyEvent.VK_META:
                //     if (isMacintosh()) controlDown = true;
                //     break;
                case Keys.Alt:
                    altDown = true;
                    // updateStatus();
                    break;
                case Keys.Shift:
                    shiftDown = true;
                    if (debugMode) beep();
                    break;
                case Keys.Space:
                {
                    spaceDown = true;
                    Gui.ImageWindow win = WindowManager.getCurrentWindow();
                    //if (win!=null) win.getCanvas().setCursor(-1,-1,-1, -1);
                    break;
                }
                case Keys.Escape:
                {
                    _escapePressed = true;
                    break;
                }
            }
        }

        public static void setKeyUp(Keys key)
        {
            //todo:
            // switch (key)
            // {
            //     case KeyEvent.VK_CONTROL: controlDown = false; break;
            //     case KeyEvent.VK_META: if (isMacintosh()) controlDown = false; break;
            //     case KeyEvent.VK_ALT: altDown = false; updateStatus(); break;
            //     case KeyEvent.VK_SHIFT: shiftDown = false; if (debugMode) beep(); break;
            //     case KeyEvent.VK_SPACE:
            //         spaceDown = false;
            //         ImageWindow win = WindowManager.getCurrentWindow();
            //         //if (win!=null) win.getCanvas().setCursor(-1,-1,-1,-1);
            //         break;
            //     case ALL_KEYS:
            //         shiftDown = controlDown = altDown = spaceDown = false;
            //         break;
            // }
        }

        /** Returns true if the <code>Esc</code> key was pressed since the
        last ImageJ command started to execute or since resetEscape() was called. */
        public static bool escapePressed()
        {
            return _escapePressed;
        }

        /** This method sets the <code>Esc</code> key to the "up" position.
        The Executer class calls this method when it runs 
        an ImageJ command in a separate thread. */
        public static void resetEscape()
        {
            _escapePressed = false;
        }

        /** Converts a number to a formatted string using
        2 digits to the right of the decimal point. */
        public static String d2s(double n)
        {
            return d2s(n, 2);
        }

        /** Converts a number to a rounded formatted string.
        The 'decimalPlaces' argument specifies the number of
        digits to the right of the decimal point (0-9). Uses
        scientific notation if 'decimalPlaces is negative. */
        public static string d2s(double n, int decimalPlaces)
        {
            if (double.IsNaN(n) || double.IsInfinity(n))
                return "" + n;
            if (n == float.MaxValue) // divide by 0 in FloatProcessor
                return "3.4e38";

            //todo:
            if (decimalPlaces < 0) decimalPlaces = 0;
            if (decimalPlaces > 9) decimalPlaces = 9;
            return n.ToString($"F{decimalPlaces}");


            // double np = n;
            // if (n < 0.0) np = -n;
            // if (decimalPlaces < 0) 
            // {
            //     decimalPlaces = -decimalPlaces;
            //     if (decimalPlaces>9) decimalPlaces=9;
            //     if (sf==null) {
            //         if (dfs==null)
            //             dfs = new DecimalFormatSymbols(Locale.US);
            //         sf = new DecimalFormat[10];
            //         sf[1] = new DecimalFormat("0.0E0", dfs);
            //         sf[2] = new DecimalFormat("0.00E0", dfs);
            //         sf[3] = new DecimalFormat("0.000E0", dfs);
            //         sf[4] = new DecimalFormat("0.0000E0", dfs);
            //         sf[5] = new DecimalFormat("0.00000E0", dfs);
            //         sf[6] = new DecimalFormat("0.000000E0", dfs);
            //         sf[7] = new DecimalFormat("0.0000000E0", dfs);
            //         sf[8] = new DecimalFormat("0.00000000E0", dfs);
            //         sf[9] = new DecimalFormat("0.000000000E0", dfs);
            //     }
            //     return sf[decimalPlaces].format(n); // use scientific notation
            // }
            // if (decimalPlaces < 0) decimalPlaces = 0;
            // if (decimalPlaces > 9) decimalPlaces = 9;
            // return df[decimalPlaces].format(n);
        }

        /** Converts a number to a rounded formatted string.
        * The 'significantDigits' argument specifies the minimum number
        * of significant digits, which is also the preferred number of
        * digits behind the decimal. Fewer decimals are shown if the 
        * number would have more than 'maxDigits'.
        * Exponential notation is used if more than 'maxDigits' would be needed.
        */
        public static string d2s(double x, int significantDigits, int maxDigits)
        {
            double log10 = Math.Log10(Math.Abs(x));
            double roundErrorAtMax = 0.223*Math.Pow(10, -maxDigits);
            int magnitude = (int)Math.Ceiling(log10+roundErrorAtMax);
            int decimals = x==0 ? 0 : maxDigits - magnitude;
            if (decimals<0 || magnitude<significantDigits+1-maxDigits)
                return IJ.d2s(x, -significantDigits); // exp notation for large and small numbers
            else
            {
                if (decimals>significantDigits)
                    decimals = Math.Max(significantDigits, decimals-maxDigits+significantDigits);
                return IJ.d2s(x, decimals);
            }
        }

        /** Pad 'n' with leading zeros to the specified number of digits. */
        public static string pad(int n, int digits)
        {
            string str = ""+n;
            while (str.Length<digits)
                str = "0"+str;
            return str;
        }

        /** Pad 's' with leading zeros to the specified number of digits. */
        public static string pad(string s, int digits)
        {
            string str = ""+s;
            while (str.Length<digits)
                str = "0"+str;
            return str;
        }

        /** Opens and displays a tiff, dicom, fits, pgm, jpeg, bmp, gif, lut, 
        roi, or text file. Displays an error message if the specified file
        is not in one of the supported formats, or if it is not found.
        With 1.41k or later, opens images specified by a URL.
        */
        public static void open(string path)
        {
            // if (_ij == null && Menus.getCommands() == null)
            //     Init();
            Opener o = new Opener();
            _macroRunning = true;
            if (path == null || path.Equals(""))
                o.open();
            else
                o.open(path);
            _macroRunning = false;
        }

        /** Opens and displays the nth image in the specified tiff stack. */
        public static void open(String path, int n)
        {
            if (_ij==null && Menus.getCommands()==null)
                init();
            ImagePlus imp = openImage(path, n);
            if (imp!=null) imp.Show();
        }

        /** Opens the specified file as a tiff, bmp, dicom, fits, pgm, gif, jpeg
         * or text image and returns an ImagePlus object if successful.
         * Calls HandleExtraFileTypes plugin if the file type is not recognised.
         * Displays a file open dialog if 'path' is null or an empty string.
         * Note that 'path' can also be a URL. Some reader plugins, including
         * the Bio-Formats plugin, display the image and return null.
         * Use IJ.open() to display a file open dialog box.
         * @see ij.io.Opener#openUsingBioFormats(String)
        */
        public static ImagePlus openImage(string path)
        {
            _macroRunning = true;
            ImagePlus imp = (new Opener()).openImage(path);
            _macroRunning = false;
            return imp;
        }

        /** Opens the nth image of the specified tiff stack. */
        public static ImagePlus openImage(String path, int n)
        {
            return (new Opener()).openImage(path, n);
        }

        /** Opens the specified tiff file as a virtual stack. */
        // public static ImagePlus openVirtual(String path)
        // {
        //     return FileInfoVirtualStack.openVirtual(path);
        // }

        /** Opens an image using a file open dialog and returns it as an ImagePlus object. */
        public static ImagePlus openImage()
        {
            return openImage(null);
        }

        /** Opens a URL and returns the contents as a string.
        Returns "<Error: message>" if there an error, including
        host or file not found. */
        // public static String openUrlAsString(String url)
        // {
        //     //if (!trustManagerCreated && url.contains("nih.gov")) trustAllCerts();
        //     url = Opener.updateUrl(url);
        //     if (debugMode) log("OpenUrlAsString: "+url);
        //     StringBuffer sb = null;
        //     url = url.replaceAll(" ", "%20");
        //     try
        //     {
        //         //if (url.contains("nih.gov")) addRootCA();
        //         URL u = new URL(url);
        //         URLConnection uc = u.openConnection();
        //         long len = uc.getContentLength();
        //         if (len>5242880L)
        //             return "<Error: file is larger than 5MB>";
        //         InputStream in = u.openStream();
        //         BufferedReader br = new BufferedReader(new InputStreamReader(in, "UTF-8"));
        //         sb = new StringBuffer();
        //         String line;
        //         while ((line=br.readLine()) != null)
        //             sb.append(line + "\n");
        //                 in.close();
        //     }
        //     catch (Exception e)
        //     {
        //         return ("<Error: "+e+">");
        //     }
        //     if (sb!=null)
        //         return new String(sb);
        //     else
        //         return "";
        // }

        /** Saves the current image, lookup table, selection or text window to the specified file path.
        The path must end in ".tif", ".jpg", ".gif", ".zip", ".raw", ".avi", ".bmp", ".fits", ".pgm", ".png", ".lut", ".roi" or ".txt".  */
        public static void save(string path)
        {
            save(null, path);
        }

        /** Saves the specified image, lookup table or selection to the specified file path.
        The file path should end with ".tif", ".jpg", ".gif", ".zip", ".raw", ".avi", ".bmp",
        ".fits", ".pgm", ".png", ".lut", ".roi" or ".txt". The specified image is saved in
        TIFF format if there is no extension. */
        public static void save(ImagePlus imp, String path)
        {
            ImagePlus imp2 = imp;
            if (imp2==null)
                imp2 = WindowManager.getCurrentImage();
            int dotLoc = path.LastIndexOf('.');
            if (dotLoc==-1 && imp2!=null)
            {
                path = path + ".tif"; // save as TIFF if file name does not have an extension
                dotLoc = path.LastIndexOf('.');
            }
            if (dotLoc!=-1)
            {
                String title = imp2!=null ? imp2.getTitle() : null;
                saveAs(imp, path.Substring(dotLoc+1), path);
                if (title!=null)
                    imp2.setTitle(title);
            }
            else
                error("The file path passed to IJ.save() method or save()\nmacro function is missing the required extension.\n \n\""+path+"\"");
        }

        /* Saves the active image, lookup table, selection, measurement results, selection XY
        coordinates or text window to the specified file path. The format argument must be "tiff",
        "jpeg", "gif", "zip", "raw", "avi", "bmp", "fits", "pgm", "png", "text image", "lut", "selection", "measurements",
        "xy Coordinates" or "text".  If <code>path</code> is null or an emply string, a file
        save dialog is displayed. */
        public static void saveAs(String format, String path)
        {
            saveAs(null, format, path);
        }

        /* Saves the specified image. The format argument must be "tiff",  
        "jpeg", "gif", "zip", "raw", "avi", "bmp", "fits", "pgm", "png", 
        "text image", "lut", "selection" or "xy Coordinates". */
        public static void saveAs(ImagePlus imp, String format, String path)
        {
            if (format==null)
                return;
            if (path!=null && path.Length==0)
                path = null;
            format = format.ToLower();
            Roi roi2 = imp!=null ? imp.getRoi() : null;
            if (roi2!=null)
                roi2.endPaste();
            // if (format.IndexOf("tif")!=-1)
            // {
            //     saveAsTiff(imp, path);
            //     return;
            // }
            // else if (format.indexOf("jpeg")!=-1 || format.indexOf("jpg")!=-1)
            // {
            //     path = updateExtension(path, ".jpg");
            //     JpegWriter.save(imp, path, FileSaver.getJpegQuality());
            //     return;
            // }
            // else if (format.indexOf("gif")!=-1)
            // {
            //     path = updateExtension(path, ".gif");
            //     GifWriter.save(imp, path);
            //     return;
            // }
            // else if (format.indexOf("text image")!=-1)
            // {
            //     path = updateExtension(path, ".txt");
            //     format = "Text Image...";
            // }
            // else if (format.indexOf("text")!=-1 || format.indexOf("txt")!=-1)
            // {
            //     if (path!=null && !path.endsWith(".xls") && !path.endsWith(".csv") && !path.endsWith(".tsv"))
            //         path = updateExtension(path, ".txt");
            //     format = "Text...";
            // }
            // else if (format.indexOf("zip")!=-1)
            // {
            //     path = updateExtension(path, ".zip");
            //     format = "ZIP...";
            // }
            // else if (format.indexOf("raw")!=-1)
            // {
            //     //path = updateExtension(path, ".raw");
            //     format = "Raw Data...";
            // }
            // else if (format.indexOf("avi")!=-1)
            // {
            //     path = updateExtension(path, ".avi");
            //     format = "AVI... ";
            // }
            // else if (format.indexOf("bmp")!=-1)
            // {
            //     path = updateExtension(path, ".bmp");
            //     format = "BMP...";
            // }
            // else if (format.indexOf("fits")!=-1)
            // {
            //     path = updateExtension(path, ".fits");
            //     format = "FITS...";
            // }
            // else if (format.indexOf("png")!=-1)
            // {
            //     path = updateExtension(path, ".png");
            //     format = "PNG...";
            // }
            // else if (format.indexOf("pgm")!=-1)
            // {
            //     path = updateExtension(path, ".pgm");
            //     format = "PGM...";
            // }
            // else if (format.indexOf("lut")!=-1)
            // {
            //     path = updateExtension(path, ".lut");
            //     format = "LUT...";
            // }
            // else if (format.contains("results") || format.contains("measurements") || format.contains("table"))
            // {
            //     format = "Results...";
            // }
            // else if (format.contains("selection") || format.contains("roi"))
            // {
            //     path = updateExtension(path, ".roi");
            //     format = "Selection...";
            // }
            // else if (format.indexOf("xy")!=-1 || format.indexOf("coordinates")!=-1)
            // {
            //     path = updateExtension(path, ".txt");
            //     format = "XY Coordinates...";
            // }
            // else
            //     error("Unsupported save() or saveAs() file format: \""+format+"\"\n \n\""+path+"\"");
            // if (path==null)
            //     run(format);
            // else
            // {
            //     if (path.contains(" "))
            //         run(imp, format, "save=["+path+"]");
            //     else
            //         run(imp, format, "save="+path);
            // }
        }

        /** Returns the path to the specified directory if <code>title</code> is
        "home" ("user.home"), "downloads", "startup",  "imagej" (ImageJ directory),
        "plugins", "macros", "luts", "temp", "current", "default",
        "image" (directory active image was loaded from), "file" 
        (directory most recently used to open or save a file), "cwd"
        (current working directory) or "preferences" (location of
        "IJ_Prefs.txt" file), otherwise displays a dialog and
        returns the path to the directory selected by the user. Returns
        null if the specified directory is not found or the user cancels the
        dialog box. Also aborts the macro if the user cancels the
        dialog box.*/
        public static string getDirectory(string title)
        {
            return null;
        }

        public static String addSeparator(String path)
        {
            if (path==null)
                return null;
            if (path.Length>0 && !(path.EndsWith(Path.DirectorySeparatorChar)||path.EndsWith("/")))
            {
                if (IJ.IsWindows()&&path.Contains(Path.DirectorySeparatorChar))
                    path += Path.DirectorySeparatorChar;
                else
                    path += "/";
            }
            return path;
        }

        /** Alias for getDirectory(). */
        public static string getDir(string title)
        {
            return getDirectory(title);
        }

        /** Switches to the specified tool, where id = Toolbar.RECTANGLE (0),
        Toolbar.OVAL (1), etc. */
        public static void setTool(int id)
        {
            ToolBar.getInstance().setTool(id);
        }

        /** Switches to the specified tool, where 'name' is "rect", "elliptical",
            "brush", etc. Returns 'false' if the name is not recognized. */
        public static bool setTool(string name)
        {
            return ToolBar.getInstance().setTool(name);
        }

        /** Returns the name of the current tool. */
        public static string getToolName()
        {
            return ToolBar.getToolName();
        }

        /** Sets the transfer mode used by the <i>Edit/Paste</i> command, where mode is "Copy", "Blend", "Average", "Difference",
        "Transparent", "Transparent2", "AND", "OR", "XOR", "Add", "Subtract", "Multiply", or "Divide". */
        public static void setPasteMode(String mode)
        {
            Roi.setPasteMode(stringToPasteMode(mode));
        }


        public static int stringToPasteMode(String mode)
        {
            if (mode==null)
                return Blitter.COPY;
            mode = mode.ToLower();
            int m = Blitter.COPY;
            if (mode.StartsWith("ble") || mode.StartsWith("ave"))
                m = Blitter.AVERAGE;
            else if (mode.StartsWith("diff"))
                m = Blitter.DIFFERENCE;
            else if (mode.IndexOf("zero")!=-1)
                m = Blitter.COPY_ZERO_TRANSPARENT;
            else if (mode.StartsWith("tran"))
                m = Blitter.COPY_TRANSPARENT;
            else if (mode.StartsWith("and"))
                m = Blitter.AND;
            else if (mode.StartsWith("or"))
                m = Blitter.OR;
            else if (mode.StartsWith("xor"))
                m = Blitter.XOR;
            else if (mode.StartsWith("sub"))
                m = Blitter.SUBTRACT;
            else if (mode.StartsWith("add"))
                m = Blitter.ADD;
            else if (mode.StartsWith("div"))
                m = Blitter.DIVIDE;
            else if (mode.StartsWith("mul"))
                m = Blitter.MULTIPLY;
            else if (mode.StartsWith("min"))
                m = Blitter.MIN;
            else if (mode.StartsWith("max"))
                m = Blitter.MAX;
            return m;
        }

        /** Returns a reference to the active image, or displays an error
        message and aborts the plugin or macro if no images are open. */
        public static ImagePlus getImage()
        {
            ImagePlus img = WindowManager.getCurrentImage();
            if (img == null)
            {
                IJ.noImage();
                if (_ij == null)
                    Environment.Exit(0);
                else
                    abort();
            }
            return img;
        }

        /**The macro interpreter uses this method to call getImage().*/
        public static ImagePlus getImage(Interpreter interpreter)
        {
            macroInterpreter = interpreter;
            ImagePlus imp = getImage();
            macroInterpreter = null;
            return imp;
        }


        /** Displays a "Process all images?" dialog. Returns
            'flags'+PlugInFilter.DOES_STACKS if the user selects "Yes",
            'flags' if the user selects "No" and PlugInFilter.DONE
            if the user selects "Cancel".
        */
        public static int setupDialog(ImagePlus imp, int flags)
        {
            if (imp == null || (_ij != null && _ij.hotkey))
            {
                if (_ij != null) _ij.hotkey = false;
                return flags;
            }
            int stackSize = imp.getStackSize();
            if (stackSize > 1)
            {
                //todo:
                // string macroOptions = Macro.getOptions();
                // if (imp.isComposite() && ((CompositeImage)imp).getMode() == IJ.COMPOSITE)
                // {
                //     if (macroOptions == null || !macroOptions.Contains("slice"))
                //         return flags | PlugInFilter.DOES_STACKS;
                // }
                // if (macroOptions != null)
                // {
                //     if (macroOptions.indexOf("stack ") >= 0)
                //         return flags | PlugInFilter.DOES_STACKS;
                //     else
                //         return flags;
                // }
                // if (hideProcessStackDialog)
                //     return flags;
                string note = ((flags & BasePlugInFilter.NO_CHANGES) == 0) ? " There is\nno Undo if you select \"Yes\"." : "";
                var dialogResult = MessageBox.Show("Process all " + stackSize + " images?" + note, "Process Stack?",
                    MessageBoxButtons.YesNoCancel);
                if (dialogResult == DialogResult.Cancel)
                    return BasePlugInFilter.DONE;
                else if (dialogResult == DialogResult.Yes)
                {
                    if (imp.getStack().isVirtual() && ((flags & BasePlugInFilter.NO_CHANGES) == 0))
                    {
                        int size = (stackSize * imp.getWidth() * imp.getHeight() * imp.getBytesPerPixel() + 524288) / 1048576;
                        string msg =
                            "Use the Process>Batch>Virtual Stack command\n" +
                            "to process a virtual stack or convert it into a\n" +
                            "normal stack using Image>Duplicate, which\n" +
                            "will require " + size + "MB of additional memory.";
                        error(msg);
                        return BasePlugInFilter.DONE;
                    }
                    // if (Recorder.record)
                    //     Recorder.recordOption("stack");
                    return flags | BasePlugInFilter.DOES_STACKS;
                }
                // if (Recorder.record)
                //     Recorder.recordOption("slice");
            }
            return flags;
        }

        public static bool statusBarProtected()
        {
            return _protectStatusBar;
        }

        public static void protectStatusBar(bool protect)
        {
            _protectStatusBar = protect;
            if (!_protectStatusBar)
                statusBarThread = null;
        }

        /** Returns the size, in pixels, of the primary display. */
        public static Size getScreenSize()
        {
            Rectangle bounds = Gui.GUI.getScreenBounds();
            return new Size(bounds.Width, bounds.Height);
        }

        static void abort()
        {
            if ((_ij != null || Interpreter.isBatchMode()) && macroInterpreter == null)
                throw new RuntimeException(Macro.MACRO_CANCELED);
        }

        /** Obsolete */
        public static void register(Type type)
        {
            if (_ij != null)
            {
                //todo:
                // _ij.register(c);
            }
        }

        /** For IDs less than zero, activates the image with the specified ID.
        For IDs greater than zero, activates the Nth image. */
        public static void selectWindow(int id)
        {
            if (id > 0)
                id = WindowManager.getNthImageID(id);
            ImagePlus imp = WindowManager.getImage(id);
            if (imp == null)
                error("Macro Error", "Image " + id + " not found or no images are open.");
            if (Interpreter.isBatchMode())
            {
                ImagePlus impT = WindowManager.getTempCurrentImage();
                ImagePlus impC = WindowManager.getCurrentImage();
                if (impC != null && impC != imp && impT != null)
                    impC.saveRoi();
                WindowManager.setTempCurrentImage(imp);
                Interpreter.activateImage(imp);
                WindowManager.setWindow(null);
            }
            else
            {
                if (imp == null)
                    return;
                Gui.ImageWindow win = imp.getWindow();
                if (win != null)
                {
                    win.BringToFront();
                    win.WindowState = FormWindowState.Normal;
                    WindowManager.setWindow(win);
                }
                long start =DateTime.Now.Ticks;
                // timeout after 1 second unless current thread is event dispatch thread
                string thread = Thread.CurrentThread.Name;
                int timeout = thread != null && thread.IndexOf("EventQueue") != -1 ? 0 : 1000;
                //todo:
                // if (IJ.isMacOSX() && IJ.isJava18() && timeout > 0)
                //     timeout = 250;  //work around OS X/Java 8 window activation bug
                while (true)
                {
                    wait(10);
                    imp = WindowManager.getCurrentImage();
                    if (imp != null && imp.getID() == id)
                        return; // specified image is now active
                    if ((DateTime.Now.Ticks - start) > timeout && win != null)
                    {
                        WindowManager.setCurrentWindow(win);
                        return;
                    }
                }
            }
        }

        /** Creates a new image.
        *  @param title   image name
        *  @param width  image width in pixels
        *  @param height image height in pixels
        *  @param depth number of stack images
        *  @param bitdepth  8, 16, 32 (float) or 24 (RGB)
        */
        public static ImagePlus createImage(String title, int width, int height, int depth, int bitdepth)
        {
            return NewImage.createImage(title, width, height, depth, bitdepth, NewImage.FILL_BLACK);
        }

        /** Creates a new imagePlus. <code>Type</code> should contain "8-bit", "16-bit", "32-bit" or "RGB". 
        In addition, it can contain "white", "black" or "ramp". <code>Width</code> 
       and <code>height</code> specify the width and height of the image in pixels.  
       <code>Depth</code> specifies the number of stack slices. */
        public static ImagePlus createImage(string title, string type, int width, int height, int depth)
        {
            type = type.ToLower();
            int bitDepth = 8;
            if (type.Contains("16"))
                bitDepth = 16;
            bool signedInt = type.Contains("32-bit int");
            if (type.Contains("32"))
                bitDepth = 32;
            if (type.Contains("24") || type.Contains("rgb") || signedInt)
                bitDepth = 24;
            int options = Gui.NewImage.FILL_WHITE;
            if (bitDepth == 16 || bitDepth == 32)
                options = Gui.NewImage.FILL_BLACK;
            if (type.Contains("white"))
                options = Gui.NewImage.FILL_WHITE;
            else if (type.Contains("black"))
                options = Gui.NewImage.FILL_BLACK;
            else if (type.Contains("ramp"))
                options = Gui.NewImage.FILL_RAMP;
            else if (type.Contains("noise") || type.Contains("random"))
                options = Gui.NewImage.FILL_NOISE;
            options += Gui.NewImage.CHECK_AVAILABLE_MEMORY;
            if (signedInt)
                options += Gui.NewImage.SIGNED_INT;
            return Gui.NewImage.createImage(title, width, height, depth, bitDepth, options);
        }

        #region result
        /** Clears the "Results" window and sets the column headings to
		those in the tab-delimited 'headings' String. Writes to
		System.out.println if the "ImageJ" frame is not present.*/
        public static void setColumnHeadings(string headings)
        {
            if (_textPanel == null && _ij != null)
                showResults();
            if (_textPanel != null)
                _textPanel.setColumnHeadings(headings);
            else
            {
                // System.out.println(headings);
            }
                
        }

        /** Returns true if the "Results" window is open. */
        public static bool isResultsWindow()
        {
            return _textPanel != null;
        }

        /**
        * @deprecated
        * replaced by IJ.log(), ResultsTable.setResult() and TextWindow.append().
        * There are examples at
        *   http://imagej.nih.gov/ij/plugins/sine-cosine.html
        */
        public static void write(string s)
        {
            if (_textPanel == null && _ij != null)
                showResults();
            if (_textPanel != null)
                _textPanel.append(s);
            else
            {
                // System.out.println(s);
            }
               
        }

        private static void showResults()
        {
            TextWindow resultsWindow = new TextWindow("Results", "", 400, 250);
            _textPanel = resultsWindow.getTextPanel();
            _textPanel.setResultsTable(Analyzer.getResultsTable());
        }

        /** Returns a reference to the "Results" window TextPanel.
        Opens the "Results" window if it is currently not open.
        Returns null if the "ImageJ" window is not open. */
        public static TextPanel getTextPanel()
        {
            if (_textPanel == null && _ij != null)
                showResults();
            return _textPanel;
        }

        /** TextWindow calls this method with a null argument when the "Results" window is closed. */
        public static void setTextPanel(TextPanel tp)
        {
            _textPanel = tp;
        }
        #endregion


        /** Causes IJ.error() output to be temporarily redirected to the "Log" window. */
        public static void redirectErrorMessages()
        {
            _redirectErrorMessages = true;
            lastErrorMessage = null;
        }

        /** Set 'true' and IJ.error() output will be temporarily redirected to the "Log" window. */
        public static void redirectErrorMessages(bool redirect)
        {
            _redirectErrorMessages = redirect;
            lastErrorMessage = null;
        }

    }
}
